{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Предсказание страхов человека по персональной анкете\n",
    "### *Автор: Станислав Наумов (GitHub: https://github.com/josdas, Slack: @josdas, email: josdas@mail.ru)*\n",
    "### Данные: https://www.kaggle.com/miroslavsabo/young-people-survey/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Описание набора данных и признаков\n",
    "\n",
    "### Вступление\n",
    "\n",
    "В 2013 году ученики FSEV UK провели опрос. Файл данных состоит из 1010 строк и 150 столбцов (139 целых и 11 категориальных).\n",
    "Данные содержат отсутствующие значения.\n",
    "\n",
    "*Все участники - граждании Словакии, в возрасте от 15 до 30 лет.*\n",
    "\n",
    "Вопросы в анкете можно разделить на следующие группы:\n",
    "\n",
    "- Музыкальные предпочтения (19 вопросов)\n",
    "- Предпочтения в кино (12 вопросов)\n",
    "- Хобби и интересы (32 вопросов)\n",
    "- Страхи (10 вопросов)\n",
    "- Здоровье (3 вопросов)\n",
    "- Черты характера, взгляды на жизнь и мнения (57 вопросов)\n",
    "- Расходы на потребление (7 вопросов)\n",
    "- Демография (10 вопросов)\n",
    "\n",
    "### Постановка задачи\n",
    "\n",
    "Задача состоит в предсказание страхов человека (10 вопросов) по всем оставшимся данным из анкеты.\n",
    "\n",
    "#### Ценность иследования: \n",
    "- Предсказание потенциальных страхов до их влияния на жизнь. Особенно актуально для подростков.\n",
    "- Выявление потенциальных признаков, способных снижать влияние страхов.\n",
    "\n",
    "### Целевые переменные:\n",
    "\n",
    "Все целевые переменные - это целые число от 1 до 5 *(1 - страха нет, 5 - сильный страх)*\n",
    "\n",
    "- Flying: страх полетов\n",
    "- Storm: боязнь штормов, грома и молнии\n",
    "- Darkness: страх темноты\n",
    "- Heights: страх высоты\n",
    "- Spiders: боязнь пауков\n",
    "- Snakes: боязнь змей\n",
    "- Rats: боязнь крыс и мышей\n",
    "- Ageing: страх старения\n",
    "- Dangerous dogs: боязнь собак\n",
    "- Fear of public speaking: страх перед публичными выступлениями"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "### Признаки\n",
    "\n",
    "Через '-' выделены различные категории, либо противоположные варианты.\n",
    "\n",
    "#### MUSIC PREFERENCES\n",
    "- Music: Я люблю слушать музыку - согласен (целое число от 1 до 5) категорически не согласен .\n",
    "- Slow songs or fast songs: Я предпочитаю - медленный темп музыки (целое число от 1 до 5) быстрый темп музыки.\n",
    "- Dance: Танец, Дискотека, фанк - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Folk: Народная музыка - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Country: Country - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Classical music: Классическая музыка - не слушаю вообще (целое от 1 до 5)нравится.\n",
    "- Musical: Мюзиклы - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Pop: Поп - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Rock: Рок - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Metal or Hardrock: Металл, хард-рок - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Punk: Панк - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Hiphop, Rap: Хип-хоп, Рэп - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Reggae, Ska: Регги, СКА - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Swing, Jazz: Свинг, Джаз - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Rock n roll: Рок-н-ролл - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Alternative: Альтернативная музыка - не нравится вообще (целое число от 1 до 5) нравится.\n",
    "- Latino: Latino - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Techno, Trance: Techno, Trance - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "- Opera: Опера - не слушаю вообще (целое от 1 до 5) нравится.\n",
    "\n",
    "#### MOVIE PREFERENCES\n",
    "- Movies: Мне очень нравится смотреть фильмы-категорически не согласен (целое число от 1 до 5).\n",
    "- Horror: Фильмы ужасов-не пользуются вообще (целое число от 1 до 5) пользуются очень много.\n",
    "- Thriller: Триллер фильмы-не пользуются вообще (целое от 1 до 5) пользуются очень много.\n",
    "- Comedy: Комедии-не пользуются вообще (целое от 1 до 5) пользуются очень много.\n",
    "- Romantic: Романтические фильмы-не пользуются вообще (целое число от 1 до 5) пользуются очень много.\n",
    "- Sci-fi: Научно-фантастические фильмы-не пользуются вообще (целое число от 1 до 5) пользуются очень много.\n",
    "- War: Военные фильмы-не пользуются вообще (целое число от 1 до 5) пользуются очень много.\n",
    "- Fantasy/Fairy tales: Сказки-не пользуются вообще (целое от 1 до 5) пользуются очень много.\n",
    "- Animated: Мультфильмы-не пользуются вообще (целое от 1 до 5) пользуются очень много.\n",
    "- Documentary: Документальные фильмы-не пользуются вообще (целое от 1 до 5) пользуются очень много.\n",
    "- Western: Западные фильмы-не пользуются вообще (целое число от 1 до 5) пользуются очень много.\n",
    "- Action: Боевики-не пользуются вообще (целое от 1 до 5) пользуются очень много.\n",
    "\n",
    "#### HOBBIES & INTERESTS\n",
    "- History: История - не интересует (целое число от 1 до 5) очень интересно.\n",
    "- Psychology: Психология-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Politics: Политика - не интересует (целое число от 1 до 5) очень интересно.\n",
    "- Mathematics: Математика-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Physics: Физика-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Internet: Интернет-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- PC: Программное обеспечение ПК, аппаратное обеспечение-не интересует (целое число от 1 до 5) очень интересно.\n",
    "- Economy Management: Экономика, Менеджмент-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Biology: Биология-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Chemistry: Химии - не интересует (целое число от 1 до 5) очень интересно.\n",
    "- Reading: Чтение стихов - не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Geography: География-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Foreign languages: Иностранные языки - не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Medicine: Медицина-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Law: Закон-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Cars: Автомобили - не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Art exhibitions: Искусство - не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Religion: Религия-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Countryside, outdoors: Активный отдых-не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Dancing: Танцы - не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Musical instruments: Игра на музыкальных инструментах - не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Writing: Стихи писать - не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Passive sport: Спорт и досуг - не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Active sport: Спорт на соревновательном уровне - не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Gardening: Садоводство - не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Celebrities: Стиль жизни знаменитостей - не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Shopping: Шопинг-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Science and technology: Наука и техника-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Theatre: Театр-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Fun with friends: Общение-не интересно (целое число от 1 до 5) очень интересно.\n",
    "- Adrenaline sports: Адреналин спорт-не интересует (целое число от 1 до 5) очень интересует.\n",
    "- Pets: Домашние животные-не заинтересованы (целое число от 1 до 5) очень заинтересованы.\n",
    "\n",
    "#### HEALTH HABITS\n",
    "- Smoking: Привычки курения - никогда не курил-пробовал курить-бывший курильщик-нынешний курильщик (категорический).\n",
    "- Alcohol: Алкоголь - не пью - иногда пью - пью много (категория).\n",
    "- Healthy eating: Я живу очень здоровым образом жизни - категорически не согласен (целое число от 1 до 5).\n",
    "\n",
    "#### PERSONALITY TRAITS, VIEWS ON LIFE & OPINIONS\n",
    "- Daily events: Я обращаю внимание на то, что происходит вокруг меня-категорически не согласен (целое число от 1 до 5).\n",
    "- Prioritising workload: Я стараюсь делать задания как можно быстрее и не оставлять их до последней минуты - категорически не согласен (целое число от 1 до 5).\n",
    "- Writing notes: Я всегда составляю список, поэтому я ничего не забываю-категорически не согласен (целое число от 1 до 5).\n",
    "- Workaholism: Я часто учусь или работаю даже в свободное время-категорически не согласен (целое число от 1 до 5).\n",
    "- Thinking ahead: Я смотрю на вещи со всех сторон, прежде чем идти вперед - категорически не согласен (целое число от 1 до 5).\n",
    "- Final judgement: Я считаю, что плохие люди будут страдать в один прекрасный день, а хорошие люди будут вознаграждены-категорически не согласны (целое число от 1 до 5).\n",
    "- Reliability: Я надежен в работе и всегда выполняю все поставленные передо мной задачи - категорически не согласен (целое число от 1 до 5).\n",
    "- Keeping promises: Я всегда выполняю свои обещания-категорически не согласен (целое число от 1 до 5).\n",
    "- Loss of interest: Я могу влюбиться в кого-то очень быстро, а затем полностью потерять интерес - категорически не согласен (целое число от 1 до 5).\n",
    "- Friends versus money: Я предпочел бы иметь много друзей, чем много денег-категорически не согласен (целое число от 1 до 5).\n",
    "- Funniness: Я всегда стараюсь быть самым смешным - категорически не согласен (целое число от 1 до 5).\n",
    "- Fake: Я могу быть двуличным иногда-категорически не согласен (целое от 1 до 5) категорически согласен.\n",
    "- Criminal damage: Я повредил вещи в прошлом, когда сердился-категорически не согласен (целое число от 1 до 5).\n",
    "- Decision making: Я не тороплюсь принимать решения-категорически не согласен (целое число от 1 до 5).\n",
    "- Elections: Я всегда стараюсь голосовать на выборах-категорически не согласен (целое число от 1 до 5).\n",
    "- Self-criticism: Я часто думаю и сожалею о решениях, которые принимаю-категорически не согласен (целое число от 1 до 5).\n",
    "- Judgment calls: Я могу сказать, слушают ли меня люди или нет, когда я говорю с ними-категорически не согласен (целое число от 1 до 5).\n",
    "- Hypochondria: Я ипохондрик-категорически не согласен (целое число от 1 до 5).\n",
    "- Empathy: Я эмоциональный человек-категорически не согласен (целое число от 1 до 5).\n",
    "- Eating to survive: Я ем, потому что должен. Я не наслаждаюсь едой и ем так быстро, как могу - категорически не согласен (целое число от 1 до 5).\n",
    "- Giving: Я стараюсь дать как можно больше другим людям на Рождество-категорически не согласен (целое число от 1 до 5).\n",
    "- Compassion to animals: Я не люблю видеть животных, страдающих-категорически не согласен (целое число от 1 до 5).\n",
    "- Borrowed stuff: Я забочусь о вещах, которые я позаимствовал у других-категорически не согласен (целое число от 1 до 5).\n",
    "- Loneliness: Я чувствую себя одиноким в жизни-категорически не согласен (целое число от 1 до 5).\n",
    "- Cheating in school: Раньше я жульничал в школе-категорически не согласен (целое число от 1 до 5) категорически согласен.\n",
    "- Health: Я беспокоюсь о своем здоровье-категорически не согласен (целое число от 1 до 5).\n",
    "- Changing the past: Я хотел бы изменить прошлое из - за того, что я сделал, - категорически не согласен (целое число от 1 до 5).\n",
    "- God: Я верю в Бога-категорически не согласен (целое число от 1 до 5).\n",
    "- Dreams: У меня всегда хорошие сны-категорически не согласен (целое число от 1 до 5) категорически согласен.\n",
    "- Charity: Я всегда отдаю на благотворительность - категорически не согласен (целое число от 1 до 5) полностью согласен.\n",
    "- Number of friends: У меня много друзей - категорически не согласен (целое число от 1 до 5) полностью согласен.\n",
    "- Punctuality: Хронометраж-я часто рано. - Я всегда вовремя. - Я часто опаздываю. (категорический.)\n",
    "- Lying: Вы лжете другим людям? - Никогда. - Только чтобы не навредить кому-то. - Иногда. - Каждый раз, когда мне это подходит. (категорический.)\n",
    "- Waiting: Я очень терпелив - категорически не согласен (целое число от 1 до 5).\n",
    "- New environment: Я могу быстро адаптироваться к новой среде-категорически не согласен (целое число от 1 до 5).\n",
    "- Mood swings: Мои настроения меняются быстро-категорически не согласен (целое число от 1 до 5) категорически согласен.\n",
    "- Appearence and gestures: Я хорошо воспитан и смотрю за своим внешним видом-категорически не согласен (целое число от 1 до 5).\n",
    "- Socializing: Мне нравится знакомиться с новыми людьми-категорически не согласен (целое число от 1 до 5).\n",
    "- Achievements: Я всегда сообщаю другим людям о моих достижениях-категорически не согласен (целое число от 1 до 5).\n",
    "- Responding to a serious letter: Я думаю, что тщательно, прежде чем отвечать на какие - либо важные письма-категорически не согласен (целое число от 1 до 5).\n",
    "- Children: Мне нравится детская компания-категорически не согласен (целое число от 1 до 5).\n",
    "- Assertiveness: Я не боюсь высказать свое мнение, если я что - то сильно чувствую-категорически не согласен (целое число от 1 до 5).\n",
    "- Getting angry: Я могу рассердиться очень легко-категорически не согласен (целое число от 1 до 5).\n",
    "- Knowing the right people: Я всегда убеждаюсь, что общаюсь с нужными людьми-категорически не согласен (целое число от 1 до 5).\n",
    "- Public speaking: Я должен быть хорошо подготовлен перед публичными выступлениями-категорически не согласен (целое число от 1 до 5).\n",
    "- Unpopularity: Я найду в себе ошибку, если я не нравлюсь людям-категорически не согласен (целое число от 1 до 5).\n",
    "- Life struggles: Я плачу, когда я чувствую себя подавленным или что - то идет не так-категорически не согласен (целое число от 1 до 5).\n",
    "- Happiness in life: Я на 100% доволен своей жизнью-категорически не согласен (целое число от 1 до 5).\n",
    "- Energy levels: Я всегда полон жизни и энергии-категорически не согласен (целое число от 1 до 5).\n",
    "- Small - big dogs: Я предпочитаю больших опасных собак меньшим, спокойным собакам-категорически не согласен (целое число от 1 до 5).\n",
    "- Personality: Я считаю, что все мои черты личности положительные-категорически не согласен (целое число от 1 до 5).\n",
    "- Finding lost valuables: Если я найду что - то, что мне не принадлежит, я передам его-категорически не согласен (целое число от 1 до 5).\n",
    "- Getting up: Мне очень трудно вставать утром-категорически не согласен (целое число от 1 до 5) категорически согласен.\n",
    "- Interests or hobbies: У меня много разных увлечений и интересов - категорически не согласен (целое число от 1 до 5) полностью согласен.\n",
    "- Parents' advice: Я всегда прислушиваюсь к советам родителей-категорически не согласен (целое число от 1 до 5).\n",
    "- Questionnaires or polls: Мне нравится принимать участие в опросах - категорически не согласен (целое число от 1 до 5).\n",
    "- Internet usage: Сколько времени вы проводите в интернете? - Нет времени вообще-меньше часа в день-несколько часов в день-большую часть дня (категория).\n",
    "\n",
    "#### SPENDING HABITS\n",
    "- Finances: Я экономлю все деньги, которые могу-категорически не согласен (целое число от 1 до 5).\n",
    "- Shopping centres: Я люблю ходить в крупные торговые центры-категорически не согласен (целое число от 1 до 5).\n",
    "- Branded clothing: Я предпочитаю брендовую одежду не брендовым-категорически не согласен (целое число от 1 до 5).\n",
    "- Entertainment spending: Я трачу много денег на вечеринки и общение-категорически не согласен (целое число от 1 до 5).\n",
    "- Spending on looks: Я трачу много денег на свою внешность - категорически не согласен (целое число от 1 до 5) полностью согласен.\n",
    "- Spending on gadgets: Я трачу много денег на гаджеты-категорически не согласен (целое число от 1 до 5).\n",
    "- Spending on healthy eating: Я буду платить больше денег за хорошее, качественное или здоровое питание-категорически не согласен (целое число от 1 до 5).\n",
    "\n",
    "#### DEMOGRAPHICS\n",
    "- Age: Возраст - целое число лет.\n",
    "- Height: Высота - целое число сантиметров.\n",
    "- Weight: Вес - целое число кг.\n",
    "- Number of siblings: Сколько у тебя братьев и сестер? - целое число\n",
    "- Gender: Пол - Женский-Мужской (категория).\n",
    "- Left - right handed: Я - левша-правша (категория).\n",
    "- Education: Высшее образование достигнуто: В настоящее время ученик начальной школы - Закончил начальную школу - Закончил среднюю школу-колледж - Получил степень бакалавра (категория).\n",
    "- Only child: Я единственный ребенок в семье: Нет-Да (категория).\n",
    "- Village - town: Большую часть своего детства я провел в: городе - деревне (категория).\n",
    "- House - block of flats: Я жил большую часть своего детства в: доме / бунгало - многоквартирном доме (категория).\n",
    "\n",
    "#### Оригинальное описание признаков вы можете найти здесь: https://www.kaggle.com/miroslavsabo/young-people-survey/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "%matplotlib inline\n",
    "\n",
    "from matplotlib import pyplot as plt\n",
    "import seaborn as sns\n",
    "import pandas_profiling\n",
    "from pylab import rcParams\n",
    "\n",
    "import os\n",
    "import pickle\n",
    "from collections import Counter\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "from scipy import stats\n",
    "\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "from sklearn.model_selection import train_test_split, KFold\n",
    "from sklearn.metrics import mean_squared_error\n",
    "from sklearn.linear_model import Lasso\n",
    "from sklearn.dummy import DummyRegressor\n",
    "from sklearn.pipeline import Pipeline, FeatureUnion, make_pipeline, make_union\n",
    "from sklearn.model_selection import cross_val_score, GridSearchCV\n",
    "from sklearn.multioutput import MultiOutputRegressor\n",
    "from sklearn.model_selection import learning_curve, validation_curve\n",
    "\n",
    "\n",
    "rcParams['figure.figsize'] = 20, 10\n",
    "pd.options.display.float_format = '{:,.3f}'.format"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "DATA_PATH = '../../data/responses.csv'\n",
    "RAND = 22"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Целевые переменные"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "targets = ['Flying', 'Storm', 'Darkness', 'Heights', 'Spiders', \n",
    "           'Snakes', 'Rats', 'Ageing', 'Dangerous dogs', 'Fear of public speaking']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Первичный анализ данных & Визуальный анализ данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Считывание данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dirty_data_df = pd.read_csv(DATA_PATH)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выделение целевых переменных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = dirty_data_df[targets]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Удаление  целевых переменных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_df = dirty_data_df.drop(targets, axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выделение категариальных признаков"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cat_feature_names = data_df.columns[data_df.dtypes == 'object']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выделение признаков, измеряющихся по 5-бальной шкале"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dropted_cat = data_df.drop(cat_feature_names, axis=1)\n",
    "limited_feature_names = dropted_cat.columns[dropted_cat.abs().max() <= 5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Анализ"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рассмотрим пропущенные значения в целевых переменных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nan_rows = y.isna().any(axis=1)\n",
    "print('Number of rows with at least one nan value in y:', nan_rows.sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В 20 анкетах **(0.02% от всех данных)** нет хотя бы одного ответа на вопрос о страхах.\n",
    "\n",
    "Удалим эти анкеты."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "indexes = y[nan_rows].index\n",
    "y = y.drop(indexes).reset_index(drop=True)\n",
    "data_df = data_df.drop(indexes).reset_index(drop=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рассмотрим пропущенные значения в признаках"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nan_rows = data_df.isna().any(axis=1)\n",
    "print('Number of rows with at least one nan value in features:', nan_rows.sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "316 анкет - это **более 30% от всех данных**, поэтому нельзя просто выкинуть эти анкеты. При дальнейшем анализе будем бороться с этими."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Проведем статические тесты на нормальность целевых переменных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pvalues_normaltest = stats.normaltest(y).pvalue\n",
    "\n",
    "pvalues_shapiro = [stats.shapiro(y[column])[1] \n",
    "                   for column in y.columns]\n",
    "\n",
    "pvalues_skewness = stats.skewtest(y).pvalue\n",
    "\n",
    "pd.DataFrame([pvalues_normaltest, pvalues_shapiro, pvalues_skewness], \n",
    "             columns=y.columns, \n",
    "             index=['normaltest', 'shapiro', 'skewness'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Гипотеза о нормальности распределения *отвергнута* для всех признаков при alpha = 0.005 (по тесту Shapiro-Wilk)\n",
    "- Гипотеза о скошенности распределения *не отвергнута* для признаков Spiders, Snakes, Dangerous dogs, Fear of public speaking при alpha = 0.005 (по тесту Skewtest)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Почему не нужно преобразовывать целевые переменны?\n",
    "- Преобразования должны одинаково влиять на все целевые перееменные, для сохранения однородности метрики.\n",
    "- Никакое детерминированное преобразование не может разделить группы с одинаковыми ответами.\n",
    "\n",
    "#### Преобразовать признаки нет смысла так как они являются ***дискретными оценками от 1 до 5***. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Посмотрим на взаимодействия целевых переменных и распределения"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pandas_profiling.ProfileReport(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Из отчета видно:\n",
    "- Все оценки являются целыми и лежат на отрезке от 1 до 5\n",
    "- **Выбросов в целевых переменных нет**\n",
    "- Некоторые страхи имеют сильную корреляцию. Например:\n",
    "    - Страх темноты и боязнь штормов\n",
    "    - Боязнь крыс, змей и пауков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Посмотрим на взаимодействия признаков с целевыми переменными\n",
    "\n",
    "\n",
    "В связи с большим количеством признаков, будем искать **top-n самых влиятельных признаков.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ARGSORT_FUNCTIONS = {\n",
    "    'decr_abs': lambda x: -np.abs(x),\n",
    "    'increase': lambda x: x,\n",
    "    'decrease': lambda x: -x,\n",
    "}\n",
    "\n",
    "def get_top_df(coefs, target_names=None, columns_names=None, top_count=5, order='decr_abs'):\n",
    "    \"\"\"\n",
    "    coefs - матрица важности. \n",
    "    На пересечение строки i со столбцом j стоит важность признака j для i.\n",
    "    \n",
    "    target_names - названия целевых переменных. Необязательный аргумент, если \n",
    "    coefs является экземпляром DataFrame. \n",
    "    \n",
    "    columns_names - названия признаков. Необязательный аргумент, если \n",
    "    coefs является экземпляром DataFrame. \n",
    "    \n",
    "    top_count - количество лучших элементов в таблице.\n",
    "    \n",
    "    order - str, определяет порядок сортировки элементов в таблице. \n",
    "    Может принимать следующие значения:\n",
    "        decr_abs - сортировка в порядке убывания по абсолютному значению\n",
    "        increase - сортировка в порядке возрастания\n",
    "        decrease - сортировка в порядке убывания\n",
    "    \n",
    "    return - возвращает DataFrame. \n",
    "    Для каждой целевой переменной top_count лучших признаков согласно заданому порядку.\n",
    "    \"\"\"\n",
    "    \n",
    "    \n",
    "    argsort_fun = ARGSORT_FUNCTIONS[order]\n",
    "    \n",
    "    if target_names is None:\n",
    "        target_names = corr_x_y.index\n",
    "        \n",
    "    if columns_names is None:\n",
    "        columns_names = corr_x_y.columns\n",
    "        \n",
    "    if isinstance(coefs, pd.DataFrame):\n",
    "        values = coefs.values\n",
    "    else:\n",
    "        values = coefs\n",
    "        \n",
    "    result = {}\n",
    "    for coef, target_name in zip(values, target_names):\n",
    "        best = argsort_fun(coef).argsort()\n",
    "\n",
    "        column = [\n",
    "            ('{}_name'.format(i), columns_names[best[i]])\n",
    "            for i in range(top_count)\n",
    "        ]\n",
    "        column += [\n",
    "            ('{}_value'.format(i), coef[best[i]])\n",
    "            for i in range(top_count)\n",
    "        ]\n",
    "\n",
    "        result[target_name] = dict(column)\n",
    "\n",
    "    return pd.DataFrame(result).T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получаем матрицу корреляций между целевыми переменными и признаками"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "merge_df = pd.concat((y, data_df), axis=1)\n",
    "corr_x_y = merge_df.corr()[targets].drop(targets).T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получаем top-5 признаков для каждой целевой переменной по **абсолютному значению корреляции.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "get_top_df(corr_x_y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Из таблицы видно:\n",
    "- в данных присутсвуют вопросы сильно связанные со страхами: \n",
    "    - Dangerous dogs - наличие собаки дома (Small - big dogs)\n",
    "    - Fear of public speaking - \"Я должен быть хорошо подготовлен перед публичным выступлением?\" (Public speaking)\n",
    "- Вес и рост имеют отрицательную корелляцию со страхом темноты (Darkness). Возможно высокие люди чувствую себя более взрослыми."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Посмотрим на взаимодействия признаков\n",
    "\n",
    "В связи с большим количеством признаков, будем искать **top-n самых скореллированных признаков.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "corr_df = data_df.corr()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Оставляем уникальные пары признак-признак и сортируем по величине корреляции"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = [\n",
    "    (corr_df[x][y], x, y)\n",
    "    for x in corr_df.columns\n",
    "    for y in corr_df.columns\n",
    "    if x < y\n",
    "]\n",
    "data.sort()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Top-20 признаков с отрицательной корреляцией"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "display(pd.DataFrame(data[:20], columns=['value', 'first', 'second']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Top-20 признаков с положительной корреляцией"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "display(pd.DataFrame(data[:-21:-1], columns=['value', 'first', 'second']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Из таблиц видно:\n",
    "- присутствуют признаки с сильной корреляцией. Например: Biology-Medicine, Shopping-Shopping centres, Punk-Rock и тд. \n",
    "    - **В дальнешем можно объединить такие признаки в один.**\n",
    "- можно выделить группы признаков, описывающие одно свойство. Например: God, Religion, Final judgement "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Посмотрим на распределения демографических признаков"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interesting_columns = data_df.columns[-10:]\n",
    "interesting_columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pandas_profiling.ProfileReport(data_df[\n",
    "    interesting_columns\n",
    "], bins=20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Из отчета видно:\n",
    "- Присутствует **один выброс** в росте 62.0 см у 20 летней девушки при весе 55кг.\n",
    "- Распределения возраста сильно смещено к 19 годам. Это может быть связано с природой выбора людей для опроса: студенты опрашивали студентов.\n",
    "- 70% людей провели детство в городе. Данное наблюдение может бысть связано место проведения опроса и уровнем урбанизации страны.\n",
    "- 60% опрошенных женского пола.\n",
    "- 61% опрошенных закончили школу, но не закончили университет. Данное наблюдение подверждает предположение о механизме выбора людей для опроса.\n",
    "- В данных присутствует 2.0% пропусков в признаках Height и Weight.\n",
    "- 15 строк-дубликатов"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Исследуем пропущенные значения признаков Height и Weight"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "height_miss = data_df[data_df['Height'].isna()].index\n",
    "weight_miss = data_df[data_df['Weight'].isna()].index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "display(height_miss)\n",
    "display(weight_miss)\n",
    "display(height_miss & weight_miss)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как мы видим из индексов, пропуски происходят не массово. Возможно люди не знали свои данные на момент анкетирования."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Посмотрим на распределения страхов по возрастам"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Размер круга на пересечение фиксированного возраста и оценки прямо пропорционален количеству точек с такими параметрами.\n",
    "\n",
    "Цвет круга изменяется от фиолетового до желтого. Цвет более желтый, если больший процент людей с таким возрастом  выбрали данную оценку."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "POINT_SIZE = 8\n",
    "\n",
    "def plot_two_columns_scatter(data, first, second, plotter=plt):\n",
    "    counter = Counter(zip(data[first], data[second]))\n",
    "    first_counter = Counter(data[first])\n",
    "    \n",
    "    triplets = np.array([(x, y, s) for (x, y), s in counter.items()])\n",
    "    nan_rows = np.isnan(triplets).any(axis=1)\n",
    "    triplets = triplets[~nan_rows]\n",
    "    \n",
    "    x, y, s = triplets.T\n",
    "    first_s = np.array([first_counter[cur_x] for cur_x in x])\n",
    "    size = s * POINT_SIZE\n",
    "    colors = s / first_s\n",
    "    \n",
    "    if plotter is plt:\n",
    "        plotter.xlabel(first)\n",
    "        plotter.ylabel(second)\n",
    "    else:\n",
    "        plotter.set_xlabel(first)\n",
    "        plotter.set_ylabel(second)\n",
    "        \n",
    "    plotter.scatter(x, y, s=size, c=colors, cmap='viridis', alpha=0.6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axarr = plt.subplots(5, 2, figsize=(12,23))\n",
    "for i in range(len(targets)):\n",
    "    plot_two_columns_scatter(dirty_data_df, \n",
    "                             first='Age', second=targets[i], \n",
    "                             plotter=axarr[i % 5, i % 2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "####  Из диаграмм видно:\n",
    "- Люди возрастом от 18 до 23 склонны отвечать 1 на пункты: Flying, Storm\n",
    "- Люди возрастом от 22 до 23  склонны отвечать 1 на пункт: Ageing\n",
    "- Люди склонны отвечать 3 на пункт: Fear of public speaking, особенно в возрасте 20 лет"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Инсайты & Наблюдения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1) Распределения возраста сильно смещено к 19 годам. \n",
    "    - Это может быть связано с природой выбора людей для опроса\n",
    "    - Предположения: \n",
    "        - Студенты опрашивали студентов\n",
    "    - Чем это полезно?\n",
    "        - Люди одной возрастной категории имеют похожие страхи\n",
    "        - Имеет смысл создать признак, отделяющий студентов по возрасту\n",
    "###  2) Присутствуют вопросы с сильной корелляцией из одной группы.\n",
    "    - Например\n",
    "        - Biology, Medicine\n",
    "        - Metal or Hardrock, Rock\n",
    "        - God, Religion, Final judgement \n",
    "    - Объяснения:\n",
    "        - Людям нравятся близкие жанры/науки/направления\n",
    "        - Некоторые вопросы очень близки по содержанию\n",
    "    - Чем это полезно?\n",
    "        - Такие признаки можно объединять в группы\n",
    "###  3) Присутствуют страхи с сильной корелляцией между собой.\n",
    "    - Например:\n",
    "        - Страх темноты и боязнь штормов\n",
    "        - Боязнь крыс, змей и пауков\n",
    "    - Объяснения:\n",
    "        - Шторм может является причиной отключения света, поэтому этот страх может быть связан с боязнью темноты\n",
    "        - Людям не нравятся хищные животные, поэтому они испытывают страх перед змеями, крысами и пауками вместе \n",
    "    - Чем это полезно?\n",
    "        - Можно попробовать уменьшить размерность пространства целевых переменных (например, при помощи PCA)\n",
    "###  4) Присутствуют вопросы с сильной корелляцией со страхами.\n",
    "    - Например\n",
    "        - Dangerous dogs - наличие собаки дома (Small - big dogs)\n",
    "        - Fear of public speaking - \"Я должен быть хорошо подготовлен перед публичным выступлением?\" (Public speaking)\n",
    "    - Объяснения:\n",
    "        - Если у человека есть собака, то он привык к лаю и знает как нужно себя вести с другими собаками\n",
    "        - Чем большую ответсвенность чувствует человек, тем больше он боится провала\n",
    "    - Чем это полезно?\n",
    "        - Данные вопросы могут являться сильными признаками для модели\n",
    "        - Возможно имеет смысл сгенерировать больше признаков на основе этих\n",
    "### 5) Пропущенные значения\n",
    "    - В 30% от всех анкет есть хотя бы один пропуск\n",
    "    - Количество пропусков в вопросах не превышает 0.41% от общего числа вопросов во всех анкетах\n",
    "    - Предположения:\n",
    "        - Люди не хотели отвечать на некоторые вопросы\n",
    "        - Проблемы с оцифровкой результатов\n",
    "    - Чем это полезно?\n",
    "        - Не стоит выкидывать анкеты хотя бы с одни пропуском (30% большая потеря)\n",
    "        - Процент пропуской довольно маленький, поэтому можно заполнить пропущенные значения медианой"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Выбор метрики \n",
    "Основные критерии метрики:\n",
    "- Регрессия\n",
    "- Несколько целевых переменных\n",
    "- Метрика может быть чувствительна к выбросам, так как в целевых признаках их нет\n",
    "- Ошибиться один раз на 2 хуже, чем два раза на 1\n",
    "- Легкость интерпритации\n",
    "\n",
    "Под все критерии подходит **mean squared error (MSE)**.\n",
    "\n",
    "Плюсы данной метрики:\n",
    "- Появляется интерпритация результатов как математическое ожидание"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Выбор модели \n",
    "\n",
    "Данные - 990 анкет по 140 вопросами. Количество признаков в 7 раз меньше, чем данных. Сильным моделям легко выучить данные, поэтому необходимо выбрать устойчивые к переобучению модели.\n",
    "\n",
    "#### **Lasso** подходит под критерии. Основные плюсы:\n",
    "- Интерпритируемая модель\n",
    "- Устойчива к переобучению \n",
    "- L1 регуляризация увеличивает количество нулевых признаков. \n",
    "    - Это позволит составить анкета направленные на выявление страхов с меньшим количеством вопросов.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Предобработка данных\n",
    "\n",
    "##### В данном блоке масштабирование НЕ проводится, так как в дальнейшем будет использован Pipline вместе с StandardScaler, где это нужно."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Уберем выбросы\n",
    "\n",
    "Заменим выбросы на nan, далее обработаем их как пропуски"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "bad_height = data_df['Height'] < 100\n",
    "data_df[bad_height]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_df = data_df.set_value(bad_height, 'Height', np.nan)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Заполним пропуски в данных \n",
    "- При помощи медианы по столбцу для целочисленных признаков.\n",
    "- Для категориальных заменем на самый частый элемент.\n",
    "\n",
    "Почему не среднее? Появятся новые нецелочисленные значения для оценок."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Nan count:', dirty_data_df.isna().sum().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Замена на медиану\n",
    "data_df.fillna(data_df.median(), inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Оставшиеся пропуски заменяем на самый частый элемент\n",
    "data_df.fillna(data_df.mode().iloc[0], inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Nan count:', data_df.isna().sum().sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Кодирование категориальных признаков методами pandas (one hot encoding)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cat_dummies_df = pd.get_dummies(data_df[cat_feature_names])\n",
    "data_df.drop(cat_feature_names, axis=1, inplace=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Удаление парных признаков. \n",
    "\n",
    "В каждой паре достаточно оставить один из элементов, второй выражается линейно как pair.first = 1 - pair.second"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cat_dummies_df.drop(['Only child_no', \n",
    "                     'House - block of flats_house/bungalow', \n",
    "                     'Village - town_city',\n",
    "                     'Only child_no', \n",
    "                     'Gender_male', \n",
    "                     'Left - right handed_right handed'], axis=1, inplace=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Объединение категориальных признаков с остальными"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_df = pd.concat((data_df, cat_dummies_df), axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Разделение на тренировочную и отложенную выборку\n",
    "Все операции, которые мы делали с данными в этом блоке не использовали целевые переменные, поэтому мы не подсматривали в ответы и может сделать разбиение на тестовую выборку.\n",
    "\n",
    "**shuffle=True - необходим**, так как неизвестна природа индексов в данных."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_df_train, data_df_test, y_train, y_test = \\\n",
    "    train_test_split(data_df, y, test_size=0.2, random_state=RAND, shuffle=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Кросс-валидация и настройка гиперпараметров модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Фиксируем разбиение для кроссвалидации.\n",
    "\n",
    "Схема кроссвалидации стандартная - 7 фолдов, обучаемся на всех кроме одно, на оствшемся тестируем. (7 - больше, чем в среднем, так как данных не много, а время работы остается приемлемым)\n",
    "\n",
    "shuffle=True - необходим, так как неизвестна природа индексов в данных."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "KF = KFold(n_splits=7, shuffle=True, random_state=RAND)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def score(model, X, y, scoring='neg_mean_squared_error'):\n",
    "    \"\"\"\n",
    "    score - средний результат на кроссвалидации модели с фиксированным разбиением.\n",
    "    \"\"\"\n",
    "    return -cross_val_score(model, X, y, scoring=scoring, cv=KF).mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Возьмем **DummyRegressor как baseline**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_model = MultiOutputRegressor(DummyRegressor())\n",
    "baseline_score = score(test_model, data_df, y)\n",
    "print('Base line score:', baseline_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В качестве базовой модели возьмем **Pipline(StandardScaler, Lasso)**\n",
    "\n",
    "**StandardScaler** - необходим для нормализации данных, так как *Lasso* - это чувствительная к масштабу линейная модель."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_base = make_pipeline(StandardScaler(), Lasso(random_state=RAND))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Перейдем к выбору гиперпараметров модели:\n",
    "- $alpha$ - основной параметр у *Lasso*, отвечает за силу $L_1$ регуляризации\n",
    "- Целью оптимизации для Lasso является: $\\frac{||y - Xw||^2_2}{2 \\cdot n\\_samples}  + alpha \\cdot ||w||_1$\n",
    "\n",
    "Для перебора параметров воспользуемся *GridSearchCV*\n",
    "- Переберем 50 параметров от 0 до 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid = {\n",
    "    'lasso__alpha': np.linspace(0, 1, 50)\n",
    "}\n",
    "\n",
    "gs_cv = GridSearchCV(model_base, grid, cv=KF, scoring='neg_mean_squared_error')\n",
    "gs_cv.fit(data_df_train, y_train)\n",
    "\n",
    "print('Best params for model:', gs_cv.best_params_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = gs_cv.best_estimator_\n",
    "cross_val_score_baseline_lasso = score(model, data_df_train, y_train)\n",
    "print('Cross val score for model:', cross_val_score_baseline_lasso)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Diff with DummyRegressor:', cross_val_score_baseline_lasso - baseline_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Создание новых признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Описание признаков:\n",
    "\n",
    "1. 'new\\__yong' и 'new\\__mid'. Воспользуемся инсайтом о распределение лет.\n",
    "    - Выделим признак позволяющий различать:\n",
    "        - школьников со старшими 'new\\__yong'\n",
    "        - взрослых со студентами и школьниками 'new\\__mid'\n",
    "    - Данные признак отделяет людей подростково возраста. У разных возрастных категорий разные страхи.\n",
    "    ***\n",
    "2. 'new\\__std'. Добавим в признаки стандартное отклонение по всем вопросам с ответами от 1 до 5.\n",
    "    - Такой признак поможет отличать людей склонных чаще отвечать разнообразно.\n",
    "    - У каждого человека своя дисперсия ответов.\n",
    "    - Некоторые люди склонны отвечать что-то среднее, а некоторые любят выбирать крайние варианты.\n",
    "    ***\n",
    "3. 'new\\__bio_group', 'new\\__rock_group', 'new\\__religion_group', 'new\\__math_group'. Объединение признаков по смысловым группам с высокой корелляцией. При объединение прежние признаки выбрасываются и добавляется их среднее.\n",
    "    - 'new\\__bio_group' содержит: 'Biology', 'Medicine'. Люди увлекающиеся медициной часто знают биологую.\n",
    "    - 'new\\__rock_group' содержит: 'Rock', 'Metal or Hardrock'. Близкие жанры.\n",
    "    - 'new\\__religion_group' содержит: 'God', 'Final judgement', 'Religion'. Высокая корелляций.\n",
    "    - 'new\\__math_group' содержит: 'Mathematics', 'Physics'. Близкие науки.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def merge_group(data, name, group):\n",
    "    \"\"\"\n",
    "    data - DataFrame таблица, из которой генерируются признаки\n",
    "    name - название нового признака имеет вид new__{name}\n",
    "    group - список названий признаков, которые нужно объединить\n",
    "    return - возвращает новый DataFrame c объединенными признаками\n",
    "    \"\"\"\n",
    "    \n",
    "    data = data.copy()\n",
    "    new_values = data[group].mean(1)\n",
    "    column_name = 'new__{}'.format(name)\n",
    "    data[column_name] = new_values\n",
    "    data = data.drop(group, axis=1)\n",
    "    return data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_new_features(data):\n",
    "    \"\"\"\n",
    "    Функция для генерации новых признаков из data\n",
    "    \"\"\"\n",
    "    \n",
    "    new_data = data.copy()\n",
    "    new_data['new__yong'] = data['Age'] < 18\n",
    "    new_data['new__mid'] = data['Age'] > 23\n",
    "    new_data['new__std'] = data[limited_feature_names].std(axis=1)\n",
    "    \n",
    "    new_data = merge_group(new_data, 'bio_group', \n",
    "                           ['Biology', 'Medicine'])\n",
    "    \n",
    "    new_data = merge_group(new_data, 'rock_group', \n",
    "                           ['Rock', 'Metal or Hardrock'])\n",
    "    \n",
    "    new_data = merge_group(new_data, 'religion_group', \n",
    "                           ['God', 'Final judgement', 'Religion'])\n",
    "    \n",
    "    new_data = merge_group(new_data, 'math_group', \n",
    "                           ['Mathematics', 'Physics'])\n",
    "    return new_data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_data_df = generate_new_features(data_df_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_base = make_pipeline(StandardScaler(), Lasso(random_state=RAND))\n",
    "\n",
    "grid = {'lasso__alpha': np.linspace(0, 1, 50)}\n",
    "gs_cv = GridSearchCV(model_base, grid, cv=KF, scoring='neg_mean_squared_error')\n",
    "gs_cv.fit(new_data_df, y_train)\n",
    "\n",
    "print('Best params for model:', gs_cv.best_params_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = gs_cv.best_estimator_\n",
    "cross_val_score_with_new_features = score(model, new_data_df, y_train)\n",
    "print('Cross val score for model with new features:', \n",
    "      cross_val_score_with_new_features)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Diff with data without new features:', \n",
    "      cross_val_score_with_new_features - cross_val_score_baseline_lasso)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Результат от добавления новых фичей стал лучше"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Построение кривых валидации и обучения "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_with_std(x, data, **kwargs):\n",
    "    \"\"\"\n",
    "    Вспомогательная функция: отрисовывает график средней величины и области +-std.\n",
    "    \"\"\"\n",
    "    mu, std = data.mean(1), data.std(1)\n",
    "    lines = plt.plot(x, mu, '-', **kwargs)\n",
    "    plt.fill_between(x, mu - std, mu + std, edgecolor='none',\n",
    "                     facecolor=lines[0].get_color(), alpha=0.4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Кривая обучения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$NMSE = -MSE$\n",
    "\n",
    "Чем больше *NMSE*, тем точнее предсказывает модели"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SCORING = 'neg_mean_squared_error'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_learning_curve(model, X, y, cv):\n",
    "    train_sizes = np.linspace(0.05, 1, 30)\n",
    "    \n",
    "    n_train, val_train, val_test = \\\n",
    "        learning_curve(model, X, y, train_sizes=train_sizes, cv=cv, scoring=SCORING)\n",
    "        \n",
    "    plot_with_std(n_train, val_train, label='training scores', c='green')\n",
    "    plot_with_std(n_train, val_test, label='validation scores', c='red')\n",
    "    \n",
    "    plt.xlabel('Training Set Size')\n",
    "    plt.ylabel(SCORING)\n",
    "    plt.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_learning_curve(model, new_data_df, y_train, cv=KF)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Как мы видим из графика:\n",
    "- Кривые стремятся к общей асимптоте\n",
    "- **Скор можно улучшить добавлением новых данных, но не сильно** \n",
    "- Для потенциально большего увеличение скора необходимо увеличить сложность модели\n",
    "- Стандартное отклонение скора маленькое"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Валидационная кривая "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Оптимизационная задача для *Lasso*: $\\frac{||y - Xw||^2_2}{2 \\cdot n\\_samples}  + alpha \\cdot ||w||_1$\n",
    "\n",
    "За сложность *Lasso* отвечает параметр *alpha*. \n",
    "Из формулы видно: чем больше *alpha*, тем проще получается модель и тем сильнее влияет регуляризация."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_valid_curve(model, X, y, cv):\n",
    "    param_range = np.linspace(0, 1, 50)\n",
    "    \n",
    "    train_scores, test_scores = \\\n",
    "        validation_curve(model, X, y, param_name='lasso__alpha', \n",
    "                         param_range=param_range, cv=cv, scoring=SCORING)\n",
    "        \n",
    "    plot_with_std(param_range, train_scores, label='training scores', c='green')\n",
    "    plot_with_std(param_range, test_scores, label='test scores', c='red')\n",
    "    \n",
    "    plt.xlabel('alpha')\n",
    "    plt.ylabel(SCORING)\n",
    "    plt.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_valid_curve(model, new_data_df, y_train, cv=KF)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Как мы видим из графика:\n",
    "- У более простых моделей с большим alpha\n",
    "    - Ошибка на тестовой и тренировочной выборке соизмерима и велика по модулю\n",
    "    - Модель недообучилась\n",
    "- У сложных моделей с мальеньким alpha\n",
    "    - Ошибка на тренировочной выборке сильно меньше ошибки на тестовой\n",
    "    - Модель переобучилась"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Прогноз для тестовой или отложенной выборке"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.fit(new_data_df, y_train)\n",
    "new_data_test_df = generate_new_features(data_df_test)\n",
    "prediction = model.predict(new_data_test_df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_score = mean_squared_error(y_test, prediction)\n",
    "print('Test score:', test_score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Cross_val score:', cross_val_score_with_new_features)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Diff:', cross_val_score_with_new_features - test_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Результат на кросс валидации соизмерим с результатом на отложенной выборке"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Вывод"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Извлекаем Lasso из Pipline, для получения коэффициентов\n",
    "lasso = model.fit(new_data_df, y_train).steps[1][1]\n",
    "coef = lasso.coef_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на суммарное количество ненулевых параметров"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Count of nonzero elements:', (coef != 0).sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на необходимое количество вопросов, чтоб определять фиксированный страх при помощи нашей модели."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "questions_count = (coef != 0).sum(axis=1)\n",
    "pd.DataFrame(questions_count, y.columns, columns=['Count of qustions'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получаем таблицу с top-5 важных признаков для Lasso модели. \n",
    "\n",
    "Данная таблица поможет нам придумать гипотезы о способах борьбы со страхами."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "get_top_df(coefs=lasso.coef_, target_names=y.columns, \n",
    "           columns_names=new_data_df.columns,  top_count=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Вывод\n",
    "Проанализирован датасет с персональными анкетами для выявления страхов людей. Выявлены интересные закономерности, на которых можно строить гипотезы о способах борьбы со страхами. Построена модель позволяет сокращать размер анкеты, что повышает шансы на то, что человек ответит на все вопросы (мало кто любит отвечать на 130 вопросов).\n",
    "\n",
    "#### Возможности применения:\n",
    "- Предсказание страхов человека по его анкете\n",
    "- Составление анкет с меньшим количеством вопросов для выявление конкретных страхов\n",
    "- Предсказание страхов человека при увеличение возраста\n",
    "\n",
    "#### Дальнейшие пути развития:\n",
    "- Отдельно исследовать детские страхи. Дети более честно отвечают на вопроса, а их страхи сильнее влияют на жизнь.\n",
    "- Исследовать большее количество стран и возрастных групп\n",
    "- Уменьшить количество вопросов в анкете для простоты исследования\n",
    "- Провести А/Б тестирование для выявленных закономерностей в данных. Например: \n",
    "    - Cобаки помогают бороться со страхом шторма\n",
    "    - Новые друзья и смена окружения помогают бороть страх публичных выступлений\n",
    "\n",
    "#### Дальнейшие пути улучшения решения:\n",
    "- Убрать лишние фичи\n",
    "- Попытаться сжать пространство ответов\n",
    "- Автоматически находить группы близких по смыслу вопросов "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [default]",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
